use std::{
    collections::HashMap,
    fs::File,
    io::Write,
    os::unix::fs::PermissionsExt,
    process::Command,
    sync::{Arc, Mutex},
};
use tokio::time::{sleep, timeout, Duration};

use model_graph_common::{
    utils::{new_uuid, rrg01_home_path},
    G01_TEMP_PATH,
};
use model_graph_types::{
    channel::CommandResult,
    container::workflow::{
        CliStatement, WorkflowBlockExecuteResult, WorkflowBlockExecuteResultBuilder,
        WorkflowBlockExecuteResultStatus,
    },
    modeling::Value,
};

use crate::workflow::context::WorkflowContext;

pub async fn execute(
    statement: &CliStatement,
    context: Arc<Mutex<WorkflowContext>>,
) -> anyhow::Result<WorkflowBlockExecuteResult> {
    let result = _execute(statement, context).await;

    match result {
        Ok(v) => Ok(WorkflowBlockExecuteResultBuilder::default()
            .status(WorkflowBlockExecuteResultStatus::Succeeded)
            .source_handle("source")
            .result(v)
            .build()?),
        Err(err) => Ok(WorkflowBlockExecuteResultBuilder::default()
            .status(WorkflowBlockExecuteResultStatus::Succeeded)
            .source_handle("fail-branch")
            .build()?),
    }
}

pub async fn _execute(
    statement: &CliStatement,
    context: Arc<Mutex<WorkflowContext>>,
) -> anyhow::Result<HashMap<String, Value>> {
    //
    let command = &statement.command;
    let arguments = &statement.arguments;
    let time_out = if let Some(t) = &statement.timeout {
        *t
    } else {
        60000_u32
    };
    let stdout = &statement.stdout;
    let stderr = &statement.stderr;

    //
    let env = HashMap::new();
    let script = command.clone();
    let current = &statement.current_dir.clone();
    let execute_result = _execute_batch_script(
        &env,
        &current,
        script,
        Some(time_out),
        stdout.clone(),
        stderr.clone(),
    )
    .await?;

    let mut result: HashMap<String, Value> = execute_result.to_value_map()?;
    //
    Ok(result)
}

async fn _execute_batch_script(
    env: &HashMap<String, String>,
    current: &Option<String>,
    script: String,
    time_out: Option<u32>,
    stdout: Option<bool>,
    stderr: Option<bool>,
) -> anyhow::Result<CommandResult> {
    //保存到临时文件
    let uuid = new_uuid()?;
    let file_name = if cfg!(target_os = "windows") {
        format!("{}.bat", uuid)
    } else {
        format!("{}.sh", uuid)
    };

    let home = rrg01_home_path()?;
    tracing::debug!("{}/{}/{}", home.display(), G01_TEMP_PATH, file_name);
    let script_file_path = format!("{}/{}/{}", home.display(), G01_TEMP_PATH, file_name);
    {
        std::fs::create_dir_all(format!("{}/{}", home.display(), G01_TEMP_PATH).as_str())?;
        let mut output = File::create(script_file_path.as_str())?;
        output.write_all(&script.as_bytes().to_vec())?;
    }

    let mut command = if cfg!(target_os = "windows") {
        Command::new("cmd")
    } else {
        Command::new("sh")
    };
    //env
    for (key, val) in env {
        command.env(key, val);
    }
    //当前目录
    if let Some(cur_dir) = current {
        command.current_dir(cur_dir.as_str());
    }

    // args
    if cfg!(target_os = "windows") {
        tracing::debug!("windows");
        command.arg("/C");
    } else {
        // Unix-like 系统（Linux/macOS）使用 chmod 风格权限
        let _ = std::fs::set_permissions(
            script_file_path.as_str(),
            std::fs::Permissions::from_mode(0o755),
        );
        command.arg("-c");
    }
    command.arg(&script_file_path.as_str());

    tracing::debug!("{:?}", command);
    //超时
    let time_out = if let Some(t) = time_out {
        t.clone() as u64
    } else {
        60000u64
    };
    //执行，需要添加超时
    if let Ok(response_result) = timeout(
        Duration::from_millis(time_out),
        Box::pin(async move {
            //发送结果
            //
            _execute_system_command(command, stdout.clone(), stderr.clone()).await
        }),
    )
    .await
    {
        let response = response_result.map_err(|err| anyhow::anyhow!("{}", err))?;
        tracing::debug!("返回数据成功!");
        return Ok(response);
    } else {
        tracing::debug!("请求超时!");
        return Err(anyhow::anyhow!("请求超时!"));
    }
}

async fn _execute_system_command(
    mut command: Command,
    stdout: Option<bool>,
    stderr: Option<bool>,
) -> anyhow::Result<CommandResult> {
    let output = command.output().map_err(|err| anyhow::anyhow!("{}", err))?;
    //
    let status = output.status;
    let stdout: Option<String> = if let Some(true) = stdout {
        Some(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        None
    };

    let stderr: Option<String> = if let Some(true) = stderr {
        Some(String::from_utf8_lossy(&output.stderr).to_string())
    } else {
        None
    };

    Ok(CommandResult {
        command: vec![],
        code: status.code().map_or(0, |v| v),
        outputs: stdout,
        errors: stderr,
    })
}
